package com.tang.plugin.model;


import com.intellij.util.xmlb.annotations.Transient;
import com.tang.plugin.utils.PluginKit;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FileTreeNode implements Serializable {

    private String name = "";
    private boolean isDir = true;
    private Map<String, String> placeHolderMap = new HashMap<>();
    private Map<String, String> fileTemplate = new HashMap<>();

    private Set<FileTreeNode> realChildren = new HashSet<>();
    private transient Map<String, FileTreeNode> labeledChildren = new HashMap<>();
    public transient FileTreeNode parent;

    private String template;

    private static final String pathSplit = "\\.";
    private static final Pattern placeHolderPattern = Pattern.compile("\\$\\{([A-Za-z0-9_]+)}");

    public FileTreeNode() {
    }

    public FileTreeNode(String name, boolean isDir, FileTreeNode parent) {
        this.name = name;
        this.isDir = isDir;
        this.parent = parent;
    }

    public String getLabel() {
        return name + "_" + isDir;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isDir() {
        return isDir;
    }

    public void setDir(boolean dir) {
        isDir = dir;
    }

    public Set<FileTreeNode> getChildren() {
        return realChildren;
    }

    public void setChildren(Set<FileTreeNode> children) {
        this.realChildren = children;
        realChildren.forEach((item) -> labeledChildren.put(item.getLabel(), item));
    }

    public Map<String, String> getPlaceHolderMap() {
        return placeHolderMap;
    }

    public void setPlaceHolderMap(Map<String, String> placeHolderMap) {
        this.placeHolderMap = placeHolderMap;
    }

    public Map<String, String> getFileTemplate() {
        return fileTemplate;
    }

    public void setFileTemplate(Map<String, String> fileTemplate) {
        this.fileTemplate = fileTemplate;
    }

    public Set<FileTreeNode> getRealChildren() {
        return realChildren;
    }

    public void setRealChildren(Set<FileTreeNode> realChildren) {
        this.realChildren = realChildren;
    }

    public boolean addChild(FileTreeNode child) {
        return addChild(child, false);
    }

    public boolean addChild(FileTreeNode child, boolean override) {
        if (hasChild(child.getLabel())) {
            if (override) {
                return false;
            } else {
                PluginKit.showErrorMessage("该路径已存在相同的文件");
            }
        }
        child.parent = this;
        realChildren.add(child);
        labeledChildren.put(child.getLabel(), child);
        return true;
    }

    public String getTemplate() {
        return template;
    }

    public void setTemplate(String template) {
        this.template = template;
    }

    public boolean hasChild(String label) {
        return labeledChildren.containsKey(label);
    }

    public boolean hasChild(String name, Boolean isDir) {
        var result = new AtomicBoolean(false);
        realChildren.forEach((it) -> {
            if (it.getName().equals(name) && isDir() == isDir) {
                result.set(true);
            }
        });
        return result.get();
    }

    public FileTreeNode copy() {
        FileTreeNode copy = new FileTreeNode(this.name, this.isDir, null);
        copy.setFileTemplate(this.fileTemplate);
        copy.setPlaceHolderMap(this.placeHolderMap);
        this.getRealChildren().forEach((item) -> copy.addChild(item.copy()));
        return copy;
    }

    public Map<String, String> getFileTemplateInherit() {
        Map<String, String> fileTemplate = getFileTemplate();
        if (fileTemplate == null || fileTemplate.isEmpty()) {
            return parent == null ? null : parent.getFileTemplateInherit();
        }
        return fileTemplate;
    }

    public List<String> getPlaceHolder(String name) {
        List<String> result = new ArrayList<>();
        try {
            Matcher matcher = placeHolderPattern.matcher(name);
            while (matcher.find()) {
                result.add(matcher.group(1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public Map<String, String> getPlaceHolderInherit() {
        Map<String, String> placeHolderMap = getPlaceHolderMap();
        if (placeHolderMap == null || placeHolderMap.isEmpty()) {
            return parent == null ? null : parent.getPlaceHolderInherit();
        }
        return placeHolderMap;
    }

    public void build() {
        if (!isDir()) {
            return;
        }
        name = getRealName();
        if (!isRoot()) {
            String[] split = name.split(pathSplit);
            List<String> dirs = new ArrayList<>();
            for (int i = 0; i < split.length; i++) {
                if (split[i] != null && split[i].length() != 0) {
                    dirs.add(split[i]);
                }
            }
            expandDirs(dirs);
        }
        getRealChildren().forEach(FileTreeNode::build);
    }

    private void expandDirs(List<String> dirs) {
        if (dirs.isEmpty() || dirs.size() == 1) {
            return;
        }

        name = dirs.remove(0);

        final FileTreeNode[] preNode = {this};
        final FileTreeNode[] newChild = new FileTreeNode[1];

        List<FileTreeNode> oldChildren = new ArrayList<>(realChildren);
        oldChildren.forEach(FileTreeNode::removeFromParent);

        dirs.forEach((it) -> {
            newChild[0] = new FileTreeNode(it, true, preNode[0]);
            preNode[0].addChild(newChild[0]);
            preNode[0] = newChild[0];
        });
        oldChildren.forEach((it) -> preNode[0].addChild(it));
    }

    public boolean removeFromParent() {
        if (parent != null) {
            parent.labeledChildren.remove(getLabel());
            parent.realChildren.remove(this);
            parent = null;
            return true;
        }
        return false;
    }

    public FileTreeDsl addFileTemplates(Map<String, String> fileTemplate) {
        if (fileTemplate == null) {
            fileTemplate = new HashMap<>();
        }
        this.fileTemplate.putAll(fileTemplate);
        return null;
    }

    public List<String> getPlaceholderInNodeName() {
        return getPlaceHolder(name);
    }

    public void addPlaceholders(String[] placeholders) {
        Map<String, String> result = new HashMap<>();
        for (String it : placeholders) {
            String value = this.placeHolderMap.get(it);
            if (value == null) {
                value = this.placeHolderMap.get("");
                if (value == null) {
                    value = "";
                }
            }
            result.put(it, value);
        }
        addPlaceholders(result);
    }

    public void addPlaceholders(Map<String, String> placeholders) {
        if (placeholders == null) {
            placeholders = new HashMap<>();
        }
        this.placeHolderMap.putAll(placeholders);
    }

    public boolean isRoot() {
        return this.equals(parent) || parent == null;
    }

    public Map<String, String> getAllPlaceHolderMap() {
        Map<String, String> result = new HashMap<>();
        if (getPlaceHolderMap() != null) {
            result.putAll(getPlaceHolderMap());
        }
        traversal(0, (node, depth) -> {
            if (node.getPlaceHolderMap() != null) {
                result.putAll(node.getPlaceHolderMap());
            }
        });
        return result;
    }

    private void traversal(int depth, TraversalCallback callback) {
        if (!isDir()) {
            return;
        }
        realChildren.forEach(it -> {
            callback.block(it, depth);
            it.traversal(depth + 1, callback);
        });
    }

    public Map<String, String> getAllFileTemplateMap() {
        Map<String, String> result = new HashMap<>();
        if (getFileTemplate() != null) {
            result.putAll(getFileTemplate());
        }
        realChildren.forEach((it) -> result.putAll(it.getAllFileTemplateMap()));
        return result;
    }

    public String getRealName() {
        String name = getName();
        if (name == null) {
            name = "";
        }
        if (isDir()) {
            return replacePlaceHolder(name, getPlaceHolderInherit(), false).toLowerCase();
        } else {
            return replacePlaceHolder(name, getPlaceHolderInherit(), true);
        }
    }

    private String replacePlaceHolder(String name, Map<String, String> placeHolders, boolean capitalize) {
        final String[] result = {name};
        if (placeHolders != null && !placeHolders.isEmpty()) {
            placeHolders.forEach((k, v) -> {
                String replace = v;
                if (capitalize) {
                    replace = replace.toLowerCase();
                }
                result[0] = result[0].replace("${" + k + "}", replace);
            });
        }
        return result[0];
    }

    public String getTemplateFile() {
        if (getTemplate() == null || getTemplate().length() == 0) {
            return getFileTemplateInherit().get(getName());
        } else {
            return getTemplate();
        }
    }

    public void removeAllPlaceholderInTree() {
        placeHolderMap.clear();
        traversal(0, (it, depth) -> it.removeAllPlaceholderInTree());
    }

    public void removeAllTemplateInTree() {
        fileTemplate.clear();
        template = null;
        traversal(0, (it, depth) -> it.removeAllTemplateInTree());
    }

    public void addFileTemplate(String fileName, String template) {
        if (fileTemplate == null) {
            fileTemplate = new HashMap<>();
        }
        fileTemplate.put(fileName, template);
    }

    public interface TraversalCallback {
        void block(FileTreeNode node, int depth);
    }
}
